.. _глава_39_start:

Исключения
==========

.. index::
    exception
    исключение

Непредвиденные ситуации являются частью поведения программ: ошибки пользователей, ошибки программирования,
изменения в программном окружении и т.д. Программы должны быть написаны таким образом, чтобы
чтобы избежать получения неверных результатов при возникновении таких исключительных условий.
Некоторые из этих условий могут быть достаточно серьезными, чтобы остановить выполнение
программы. Например, необходимая информация может отсутствовать или быть недействительной,
или устройство может работать неправильно. Механизм обработки исключений
D помогает остановить выполнение программы, когда это необходимо, и восстановиться после
неожиданных ситуаций, когда это возможно.
В качестве примера тяжелого состояния можно привести передачу неизвестной
операции в функцию, которая знает только четыре арифметических операции,
как мы уже видели в упражнениях предыдущей главы:

.. code-block:: d

    switch (operator)
    {
    case "+" : writeln(first + second);
        break;
    case "-" : writeln(first - second);
        break;
    case "x" : writeln(first * second);
        break;
    case "/" : writeln(first / second);
        break;
    default : throw new Exception(format("Invalid operator: %s", operator));
    }


Приведенный выше оператор switch не знает, что делать с операторами, которые
не перечислены в операторах case, поэтому выбрасывает исключение.
В Phobos есть много примеров выброшенных исключений. Например,
to!int, который может быть использован для преобразования строкового представления целого числа в значение
int, выбрасывает исключение, если это представление не является допустимым:

.. code-block:: d

    import std.conv;

    void main()
    {
        const int value = to!int("hello");
    }

Программа завершается с исключением, которое выбрасывает to!int:

.. code-block:: console

    std.conv.ConvException@std/conv.d(38): std.conv(1157): Can't convert value `hello' of type const(char)[] to type int

std.conv.ConvException в начале сообщения - это тип
выброшенного объекта исключения. Из имени можно понять, что тип
ConvException, который определен в модуле std.conv.

.. index::
    throw

Оператор throw для выбрасывания исключений
------------------------------------------

Мы уже встречались с оператором throw как в примерах выше, так и в предыдущих
главах. throw выбрасаывает объект исключения, что приводит к завершению текущей операции
программы. Выражения и операторы, записанные после оператора throw
не выполняются. Такое поведение соответствует природе
исключений: они должны выбрасываться, когда программа не может продолжить выполнение своей
текущей задачи.
И наоборот, если бы программа могла продолжить работу, то ситуация не
требовала бы создания исключения. В таких случаях функция нашла бы способ и
продолжила бы работу.

.. index::
    Exception
    Error

**Типы исключений Exception и Error**

Выбрасывать можно только те типы, которые наследуются от класса Throwable.
Throwable почти никогда не используется непосредственно в программах. Типы, которые действительно
выбрасываются - это типы, унаследованные от Exception или Error, которые сами
являются типами, унаследованными от Throwable. Например, все
исключения, которые выбрасывает Phobos, наследуются либо от Exception, либо от Error.Error представляет собой неустранимую ошибку и перехват не рекомендуется
По этой причине большинство исключений, которые выбрасывает программа, относятся к типам
типы, которые наследуются от Exception. (Примечание: Наследование - это тема, связанная с
классами. Мы рассмотрим классы в одной из следующих глав.)
Объекты Exception конструируются со строковым значением, которое представляет собой
сообщение об ошибке. Вы можете легко создать это сообщение с помощью функции format()
из модуля std.string:

.. code-block:: d

    import std.stdio;
    import std.random;
    import std.string;

    int[] randomDiceValues(int count)
    {
        if (count < 0)
        {
            throw new Exception(format("Invalid dice count: %s", count));
        }
        int[] values;
        foreach (i; 0 .. count)
        {
            values ~= uniform(1, 7);
        }
        return values;
    }

    void main()
    {
        writeln(randomDiceValues(-5));
    }


.. code-block:: console

    object.Exception...: Invalid dice count: -5

В большинстве случаев вместо того, чтобы явно создавать объект исключения с помощью new и
бросать его с помощью throw, вызывается функция enforce(). Например,
эквивалентом приведенной выше проверки ошибки является следующий вызов enforce():

.. code-block:: d

    enforce(count >= 0, format("Invalid dice count: %s", count));

Различия между enforce() и assert() мы рассмотрим в одной из следующих глав.

**Выброшенное исключение завершает работу всех областей**

Мы видели, что выполнение программы начинается с главной функции и
а затем разветвляется на другие функции. Это многоуровневое выполнение, когда вы углубляетесь в функции и в конце концов возвращаетесь из них, можно представить как ветви
дерева.
Например, функция main() может вызвать функцию makeOmelet, которая, в свою очередь
может вызвать другую функцию с именем prepareAll, которая, в свою очередь, может вызвать другую
функцию с именем prepareEggs и т. д. Если предположить, что стрелки указывают на вызовы функций
стрелками обозначены вызовы функций, ветвление такой программы может быть показано следующим образом
дерево вызовов:



.. image:: images/39.1_1.png



Следующая программа демонстрирует вышеописанное ветвление, используя различные
уровней отступов в своем выводе. Программа не делает ничего полезного, кроме
кроме вывода данных, подходящих для наших целей:

.. code-block:: d

    import std.stdio;

    void indent(int level)
    {
        foreach (i; 0 .. level * 2)
        {
            write(' ');
        }
    }

    void entering(string functionName, int level)
    {
        indent(level);
        writeln("> ", functionName, "'s first line");
    }

    void exiting(string functionName, int level)
    {
        indent(level);
        writeln("< ", functionName, "'s last line");
    }

    void main()
    {
        entering("main", 0);
        makeOmelet();
        eatOmelet();
        exiting("main", 0);
    }

    void makeOmelet()
    {
        entering("makeOmelet", 1);
        prepareAll();
        cookEggs();
        cleanAll();
        exiting("makeOmelet", 1);
    }

    void eatOmelet()
    {
        entering("eatOmelet", 1);
        exiting("eatOmelet", 1);
    }

    void prepareAll()
    {
        entering("prepareAll", 2);
        prepareEggs();
        prepareButter();
        preparePan();
        exiting("prepareAll", 2);
    }

    void cookEggs()
    {
        entering("cookEggs", 2);
        exiting("cookEggs", 2);
    }

    void cleanAll()
    {
        entering("cleanAll", 2);
        exiting("cleanAll", 2);
    }

    void prepareEggs()
    {
        entering("prepareEggs", 3);
        exiting("prepareEggs", 3);
    }

    void prepareButter()
    {
        entering("prepareButter", 3);
        exiting("prepareButter", 3);
    }

    void preparePan()
    {
        entering("preparePan", 3);
        exiting("preparePan", 3);
    }


Вывод:

.. code-block:: console

    > main, first line
        > makeOmelet, first line
            > prepareAll, first line
                > prepareEggs, first line
                < prepareEggs, last line
                > prepareButter, first line
                < prepareButter, last line
                > preparePan, first line
                < preparePan, last line
            < prepareAll, last line
            > cookEggs, first line
        < cookEggs, last line
        > cleanAll, first line
        < cleanAll, last line
        < makeOmelet, last line
        > eatOmelet, first line
        < eatOmelet, last line
    < main, last line

Функции enter и exiting используются для указания первой и последней строк
функций с помощью символов > и <. Программа начинается с
первой строки main(), переходит к другим функциям и, наконец, завершается
последней строкой main.
Давайте изменим функцию prepareEggs, чтобы она принимала в качестве параметра количество яиц
параметр. Поскольку некоторые значения этого параметра будут являться ошибкой, давайте сделаем так.
чтобы эта функция выбрасывала исключение, если количество яиц меньше единицы:

.. code-block:: d

    import std.string;

    // ...
    void prepareEggs(int count)
    {
        entering("prepareEggs", 3);
        if (count < 1)
        {
            throw new Exception(format("Cannot take %s eggs from the fridge", count));
        }
        exiting("prepareEggs", 3);
    }

Чтобы скомпилировать программу, мы должны изменить другие строки
программы, чтобы они были совместимы с этим изменением. Количество яиц, которые нужно достать из
холодильника, можно передавать из функции в функцию, начиная с main(). На сайте
части программы, которые необходимо изменить, следующие. Недопустимое значение -8
намеренно, чтобы показать, как будет отличаться вывод программы от
при возникновении исключения:

.. code-block:: d

    // ...
    void main()
    {
        entering("main", 0);
        makeOmelet(-8);
        eatOmelet();
        exiting("main", 0);
    }

    void makeOmelet(int eggCount)
    {
        entering("makeOmelet", 1);
        prepareAll(eggCount);
        cookEggs();
        cleanAll();
        exiting("makeOmelet", 1);
    }
    // ...
    void prepareAll(int eggCount)
    {
        entering("prepareAll", 2);
        prepareEggs(eggCount);
        prepareButter();
        preparePan();
        exiting("prepareAll", 2);
    }
    // ...

Запустив программу сейчас, мы увидим, что строки, которые раньше выводились после
после точки, в которой возникает исключение, пропали:

.. code-block:: console

     > main, first line
         > makeOmelet, first line
         > prepareAll, first line
                 > prepareEggs, first line
    object.Exception: Cannot take -8 eggs from the fridge


При возникновении исключения программа выходит из функций prepareEggs,
prepareAll, makeOmelet и main() в таком порядке, от нижнего
уровня к верхнему. При выходе из этих функций не выполняется никаких дополнительных шагов
функций.
Обоснованием такого радикального завершения является то, что сбой в функции нижнего уровня
означает, что функции более высокого уровня, которые нуждались в его успешном
также должны быть признаны неудавшимися.
Объект исключения, выброшенный из функции нижнего уровня, передается в
функции более высокого уровня по одному за раз и приводит к окончательному
выход из функции main(). Путь, который проходит исключение, может быть показан как
выделенный путь в следующем дереве:

.. image:: images/39.1_2.png

Смысл механизма исключений как раз и заключается в том, чтобы выходить из всех
слоев вызовов функций сразу же.
Иногда имеет смысл перехватить брошенное исключение, чтобы найти способ
продолжить выполнение программы. Ключевое слово catch будет представлено ниже.

**Когда использовать throw**

Используйте throw в ситуациях, когда продолжение невозможно. Например.
функция, которая считывает количество студентов из файла, может выбросить исключение, если
если эта информация недоступна или неверна.
С другой стороны, если проблема вызвана каким-то действием пользователя, например вводом
недопустимых данных, то, возможно, имеет смысл проверить данные, а не выбрасывать
исключение. Вывод сообщения об ошибке и предложение пользователю повторно ввести данные
во многих случаях более уместно.

Оператор try-catch для перехвата исключений
-------------------------------------------

Как мы видели выше, брошенное исключение приводит к завершению выполнения программы всеми
функций, что окончательно завершает работу всей программы.
Объект исключения может быть пойман оператором try-catch в любой точке
когда он выходит из функций. Оператор try-catch моделирует фразу "попытаться
сделать что-то и поймать исключения, которые могут быть выброшены". Вот синтаксис
try-catch:

.. code-block:: d

    try
    {
        // выполняемый блок кода, в котором
        // может быть выброшено исключение
    }
    catch (an_exception_type)
    {
        // выражения для выполнения, если исключение данного типа
    }
    catch (another_exception_type)
    {
        // выражения для выполнения, если поймано исключение этого // другого типа
        // ... больше блоков catch по мере необходимости ...
    }
    finally
    {
        // выражения для выполнения независимо от того.
        // выброшено ли исключение
    }


Чтобы понять этот блок, сначала посмотрите на следующую программу, в которой не используется try-catch
Описание. Эта программа считывает значение кубика из файла и печатает его на выходе:

.. code-block:: d

    import std.stdio;

    int readDieFromFile()
    {
        auto file = File("the_file_that_contains_the_value", "r");
        int die;
        file.readf(" %s", &die);
        return die;
    }

    void main()
    {
        const int die = readDieFromFile();
        writeln("Die value: ", die);
    }


Обратите внимание, что функция readDieFromFile написана таким образом, что игнорирует ошибки ожидая, что файл и содержащееся в нем значение доступны.В
другими словами, функция занимается только своей задачей, а не обращает
внимание на условия ошибки. В этом заключается преимущество исключений: многие функции можно
могут быть написаны таким образом, чтобы сосредоточиться на своих реальных задачах, а не на условиях ошибки.
условия.
Давайте запустим программу, когда файл the_file_that_contains_the_value отсутствует

.. code-block:: console

    std.exception.ErrnoException@std/stdio.d(286): Cannot openfile `the_file_that_contains_the_value' in mode `r' (No such file or directory)

Возникает исключение типа ErrnoException, и программа завершается не напечатав "Die value:
".Добавим в программу промежуточную функцию, которая вызывает readDieFromFile
из блока try, и пусть main() вызывает эту новую функцию:

.. code-block:: d

    import std.stdio;
    import std.exception;

    int readDieFromFile()
    {
        auto file = File("the_file_that_contains_the_value", "r");
        int die;
        file.readf(" %s", &die);
        return die;
    }

    int tryReadingFromFile()
    {
        int die;
        try
        {
            die = readDieFromFile();
        }
        catch (std.exception.ErrnoException exc)
        {
            writeln("(Could not read from file; assuming 1)");
            die = 1;
        }
        return die;
    }

    void main()
    {
        const int die = tryReadingFromFile();
        writeln("Die value: ", die);
    }

Когда мы снова запускаем программу, когда файл_который_содержит_значение по-прежнему отсутствует, на этот раз программа не завершается с исключением:

.. code-block:: console

    (Could not read from file; assuming 1) Die value: 1

Новая программа пытается выполнить readDieFromFile в блоке try. Если этот блок
выполняется успешно, функция завершается обычным образом с помощью оператора return die;
оператор. Если выполнение блока попыток завершается указанным
std.exception.ErrnoException, то выполнение программы переходит в блок catch
блок.
Ниже приведена сводка событий при запуске программы, когда
файл отсутствует:

* как и в предыдущей программе, объект std.exception.ErrnoException
  бросается (командой File(), а не нашим кодом),
* это исключение перехватывается командой catch,
* во время нормального выполнения блока catch принимается значение 1,
* и программа продолжает свою обычную работу.

Блок catch предназначен для перехвата выброшенных исключений, чтобы найти способ продолжить выполнение
программы.
В качестве другого примера вернемся к программе omelet и добавим оператор try-catch
в функцию main():

.. code-block:: d

    void main()
    {
        entering("main", 0);
        try
        {
            makeOmelet(-8);
            eatOmelet();
        }
        catch (Exception exc)
        {
            write("Failed to eat omelet: ");
            writeln('"', exc.msg, '"');
            writeln("Will eat at neighbor's...");
        }
        exiting("main", 0);
    }


*(Примечание: Свойство .msg будет описано ниже.)*
Блок try содержит две строки кода. Любое исключение, выброшенное из любой из
этих строк, будет поймано блоком catch.

.. code-block:: console

    > main, first line
        > makeOmelet, first line
            > prepareAll, first line
                > prepareEggs, first line
    Failed to eat omelet: "Cannot take -8 eggs from the fridge" Will eat at neighbor's...
    < main, last line

Как видно из вывода, программа больше не завершается из-за
выброшенного исключения. Она восстанавливается после ошибки и продолжает
нормально выполняться до конца функции main().

**Блоки catch рассматриваются последовательно**

Тип Exception, который мы до сих пор использовали в примерах, является общим
тип исключения. Этот тип просто указывает, что в программе произошла ошибка.
Он также содержит сообщение, которое может объяснить ошибку, но не содержит
ConvException и ErrnoException, которые мы рассматривали ранее в этой главе, являются более специфическими типами исключений.
являются более конкретными типами исключений: первое относится к ошибке преобразования, а второе - к системной ошибке.
второй - о системной ошибке. Как и многие другие типы исключений в Phobos и, как следует из их названий
как следует из их названий, ConvException и ErrnoException наследуются
от класса Exception.
Exception и его брат Error наследуются от Throwable, общего типа исключений.
наиболее общего типа исключений.
Хотя это возможно, не рекомендуется ловить объекты типа Error и
объекты типов, унаследованных от Error. Поскольку этот тип является более общим, чем Error, не рекомендуется ловить и Throwable. Обычно


ловить типы, которые находятся в иерархии Exception, включая
Само исключение.

.. image:: images/39.2_1.png

Примечание: Представления иерархии я объясню позже в главе "Наследование" (:ref:`Наследование<глава_55_start>`.
Дерево выше показывает, что Throwable является наиболее общим, а Exception и
Error - более конкретные.
Можно отлавливать объекты исключений определенного типа. Например.
можно поймать объект ErrnoException, специально предназначенный для обнаружения и обработки
системной ошибки.
Исключения перехватываются только в том случае, если они соответствуют типу, указанному в блоке catch
блоке. Например, блок catch, который пытается поймать SpecialExceptionType
не поймает исключение ErrnoException.
Тип объекта исключения, который выбрасывается во время выполнения блока try
блока, сопоставляется с типами, указанными блоками catch, в порядке
в котором записаны блоки catch. Если тип объекта совпадает с типом блока catch, то исключение считается пойманным этим блоком catch
блоком, и код, находящийся в этом блоке, будет выполнен. Как только совпадение найдено,
остальные блоки catch игнорируются.
Поскольку блоки catch сопоставляются в порядке от первого до последнего, блоки catch
блоки catch должны быть упорядочены от наиболее специфических типов исключений к наиболее
общим типам исключений. Соответственно, если имеет смысл перехватывать именно этот тип
исключений, тип Exception должен быть указан в последнем блоке catch.
Например, оператор try-catch, который пытается поймать несколько специфических
типов исключений, связанных с записями студентов, должен упорядочить блоки перехвата от
наиболее специфических к более общим

.. code-block:: d

    try
    {
        // operations about student records that may throw ...
    }
    catch (StudentIdDigitException exc)
    {
        // an exception that is specifically about errors with
        // the digits of student ids
    }
    catch (StudentIdException exc)
    {
        // a more general exception about student ids but not
        // necessarily about their digits
    }
    catch (StudentRecordException exc)
    {
        // even more general exception about student records
    }
    catch (Exception exc)
    {
        // the most general exception that may not be related
        // to student records
    }


**Блок finally**

finally - это необязательный блок оператора try-catch. Он включает выражения
которые должны быть выполнены независимо от того, было ли выброшено исключение или нет.
Чтобы увидеть, как работает finally, давайте рассмотрим программу, которая выбрасывает исключение
в 50 % случаев:

.. code-block:: d

    import std.stdio;
    import std.random;

    void throwsHalfTheTime()
    {
        if (uniform(0, 2) == 1)
        {
            throw new Exception("the error message");
        }
    }

    void foo()
    {
        writeln("the first line of foo()");
        try
        {
            writeln("the first line of the try block");
            throwsHalfTheTime();
            writeln("the last line of the try block");
            // ... there may be one or more catch blocks here ...
        }
        finally
        {
            writeln("the body of the finally block");
        }
        writeln("the last line of foo()");
    }

    void main()
    {
        foo();
    }

Если функция не выбрасывает исключение, то программа выдает следующий результат:

.. code-block:: console

        the first line of foo()
        the first line of the try block
        the last line of the try block
        the body of the finally block
        the last line of foo()

Если функция выбрасывает исключение, то программа выдает следующий результат:

.. code-block:: console

    the first line of foo()
    the first line of the try block
    the body of the finally block
    object.Exception@deneme.d: the error message

Как видно, хотя "последняя строка блока try" и "последняя строка foo()"
не выводятся, содержимое блока finally все равно выполняется, когда выбрасывается
исключения.

**Когда использовать оператор try-catch**

Оператор try-catch полезен для перехвата исключений, чтобы сделать с ними что-то особенное
По этой причине оператор try-catch следует использовать только в тех случаях, когда
необходимо сделать что-то особенное. В противном случае не ловите исключения и предоставьте их
функции более высокого уровня, которые могут захотеть их поймать.

Свойства исключений
-------------------

.. index::
    cвойства исключений
    .file
    .line
    .msg
    .info
    .next

Информация, которая автоматически выводится на экран, когда программа
при завершении программы из-за исключения, доступна в качестве свойств объектов исключений.
также. Эти свойства предоставляются интерфейсом Throwable:

* .file: Исходный файл, из которого было выброшено исключение
* .line: Номер строки, из которой было выброшено исключение
* .msg: Сообщение об ошибке
* .info: Состояние стека программы в момент возникновения исключения
* .next: Следующее сопутствующее исключение

Мы видели, что блоки finally выполняются при выходе из области действия из-за исключений
также. (Как мы увидим в последующих главах, то же самое справедливо для операторов области видимости
и деструкторов.)
Естественно, такие блоки кода тоже могут выбрасывать исключения. Исключения, которые
возникающие при выходе из области видимости из-за уже возникшего исключения, называются
сопутствующими исключениями. И главное исключение, и сопутствующие исключения являются
элементами структуры данных связанного списка, где каждый объект исключения доступен
через свойство .next предыдущего объекта исключения. Значение свойства
.next последнего исключения равно null. (Мы рассмотрим null в одной из следующих глав).
В примере, приведенном ниже, брошены три исключения: Основное
исключение, возникающее в foo(), и два побочных исключения, которые
в блоках finally функций foo() и bar(). Программа получает доступ к
сопутствующим исключениям через свойства .next.
Некоторые концепции, используемые в этой программе, будут объяснены в последующих
главах. Например, условие продолжения цикла for, состоящее
только из exc, означает, что пока exc не равно null.

.. code-block:: d

    import std.stdio;

    void foo()
    {
        try
        {
            throw new Exception("Exception thrown in foo");
        }
        finally
        {
            throw new Exception("Exception thrown in foo's finally block");
        }
    }

    void bar()
    {
        try
        {
            foo();
        }
        finally
        {
            throw new Exception("Exception thrown in bar's finally block");
        }
    }

    void main()
    {
        try
        {
            bar();
        }
        catch (Exception caughtException)
        {
            for (Throwable exc = caughtException; exc;  // ← Meaning: as long as exc is not 'null'  exc = exc.next) {
                writefln("error message: %s", exc.msg); writefln("source file : %s", exc.file);
                writefln("source line : %s", exc.line); writeln();
            }
        }
    }

Вывод

.. code-block:: console

    error message: Exception thrown in foo
    source file : deneme.d
    source line : 6
    error message: Exception thrown in foo's finally block
    source file : deneme.d
    source line : 9
    error message: Exception thrown in bar's finally block
    source file : deneme.d
    source line : 20

Виды ошибок
------------

Мы увидели, насколько полезен механизм исключений. Он позволяет прерывать как операции нижнего
и более высокого уровня сразу же прервать выполнение операций, вместо того чтобы программа
продолжать работу с неверными или отсутствующими данными, или вести себя любым другим неправильным
образом.
Это не означает, что каждое состояние ошибки оправдывает выброс исключения.
В зависимости от типа ошибки можно найти более подходящее решение.

**Ошибки пользователя**

Некоторые ошибки возникают по вине пользователя. Как мы видели выше, пользователь может
ввести строку типа "hello", хотя программа ожидала увидеть
число. В этом случае целесообразнее вывести сообщение об ошибке и попросить пользователя
ввести соответствующие данные еще раз.
Но даже в этом случае можно принять и использовать данные напрямую, не проверяя их
Если код, который использует эти данные, все равно их отбросит.
Важно иметь возможность уведомить пользователя о том, что данные не подходят.
Например, рассмотрим программу, которая принимает от пользователя имя файла. Существует
Существует как минимум два способа борьбы с потенциально недопустимыми именами файлов:

* Проверка данных перед использованием: Мы можем определить, существует ли файл с
  существует, вызвав функцию exists() из модуля std.file:

.. code-block:: d

    if (exists(fileName))
    {
        // yes, the file exists
    }
    else
    {
        // no, the file doesn't exist
    }

Это дает нам возможность открыть данные только в том случае, если они существуют.
К сожалению, существует вероятность того, что файл не может быть открыт, даже если
exists() возвращает true, если, например, другой процесс в системе удалит
или переименует файл до того, как эта программа откроет его.
По этой причине следующий метод может оказаться более полезным.

* Использование данных без их предварительной проверки: Мы можем предположить, что данные
  и сразу же начать использовать их, потому что File выбросит исключение, если
  файл все равно не может быть открыт

.. code-block:: d

    import std.stdio;
    import std.exception;
    import std.string;

    void useTheFile(string fileName)
    {
        auto file = File(fileName, "r");
        // ...
    }

    string read_string(string prompt)
    {
        write(prompt, ": ");
        return strip(readln());
    }

    void main()
    {
        bool is_fileUsed = false;
        while (!is_fileUsed)
        {
            try
            {
                useTheFile(read_string("Please enter a file name"));

                /* If we are at this line, it means that
            * useTheFile() function has been completed
            * successfully. This indicates that the file
            * name was valid.
            *
            * We can now set the value of the loop flag to
            * terminate the while loop. */
                is_fileUsed = true;
                writeln("The file has been used successfully");
            }
            catch (std.exception.ErrnoException exc)
            {
                stderr.writeln("This file could not be opened");
            }
        }
    }

**Ошибки программистов**

Некоторые ошибки вызваны ошибками программистов. Например, программист
может думать, что только что написанная функция всегда будет вызываться со значением
значением, большим или равным нулю, и это может быть верно в соответствии с дизайном
программы. Если же функция все же была вызвана со значением меньше нуля
указывает либо на ошибку в дизайне программы, либо на
реализации этого дизайна. И то и другое можно рассматривать как ошибки программирования
ошибки.
Уместнее использовать assert вместо механизма исключений для
ошибок, вызванных ошибками программистов. (Примечание: мы рассмотрим assert в
последующей главе (:ref:`assert и enforce<глава assert и enforce>`)).

.. code-block:: d

    void processMenuSelection(int selection)
    {
        assert(selection >= 0);
        // ...
    }

    void main()
    {
        processMenuSelection(-1);
    }

Программа завершается с ошибкой утверждения:

.. code-block:: console

    core.exception.AssertError@deneme.d(2): Assertion failure

assert проверяет состояние программы и печатает имя файла и номер строки, на которой произошла проверка.
в случае неудачи. Приведенное выше сообщение указывает на то, что утверждение в строке 2 файла
deneme.d не удалось.

**Неожиданные ситуации**

Для непредвиденных ситуаций, которые выходят за рамки двух общих случаев, описанных выше, все же уместно
уместно бросать исключения. Если программа не может продолжить выполнение,
то ничего другого не остается, как выбросить исключение.
Функции верхнего уровня, вызывающие эту функцию, сами решают, что делать
с выброшенными исключениями. Они могут перехватить исключения, которые мы бросаем, чтобы исправить
ситуацию.

Резюме
------

* При возникновении ошибки пользователя либо сразу предупредите его, либо убедитесь, что
  исключение; исключение может быть вызвано другой
  исключение может быть выброшено другой функцией при использовании неверных данных, или вы можете выбросить его напрямую.
* Используйте assert для проверки логики и реализации программы. (Примечание: assert будет
  будет рассказано в следующей главе).
* Если вы сомневаетесь, выбросьте исключение с помощью throw или enforce(). (Примечание:
  enforce() будет описано в следующей главе).
* Ловите исключения тогда и только тогда, когда вы можете сделать что-то полезное с этим
  исключением. В противном случае не инкапсулируйте код с помощью оператора try-catch;
  вместо этого предоставьте исключения более высоким уровням кода, которые могут сделать
  что-то с ними сделать.
* Располагайте блоки try catch в порядке от наиболее специфических к наиболее общим.
* Поместите выражения, которые всегда должны выполняться при выходе из области видимости, в
  блоки finally.
